[PT Run][Time and Date] Add friendly date/time result (#16809)#46803
[PT Run][Time and Date] Add friendly date/time result (#16809)#46803RealPratham21 wants to merge 1 commit intomicrosoft:mainfrom
Conversation
|
@microsoft-github-policy-service agree |
There was a problem hiding this comment.
Pull request overview
Adds a new “Friendly” output to the PT Run Time and Date plugin, intended to display human-readable relative phrases (e.g., “Yesterday”, “in 3 hours”, “4 hours ago”) alongside existing date/time formats.
Changes:
- Adds new localized strings and search tags for the Friendly format.
- Introduces
TimeAndDateHelper.GetFriendlyDateTime(target, referenceNow)and wires it intoAvailableResultsList.GetList(...)via a new optionalnowparameter for deterministic tests. - Updates docs and extends unit tests (result counts, icons, helper tests).
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.resx | Adds Friendly label/phrases and search tags. |
| src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs | Regenerates strongly-typed accessors for new resources. |
| src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/TimeAndDateHelper.cs | Adds GetFriendlyDateTime helper. |
| src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Components/AvailableResultsList.cs | Adds Friendly result and optional now param for tests. |
| src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeDateResultTests.cs | Updates calls to new GetList signature. |
| src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/TimeAndDateHelperTests.cs | Adds data-driven tests for Friendly output. |
| src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/QueryTests.cs | Updates expected result counts for keyword searches. |
| src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/ImageTests.cs | Adds icon assertions for Friendly results (dark/light). |
| doc/devdocs/modules/launcher/plugins/timedate.md | Documents the Friendly format in the formats table. |
Files not reviewed (1)
- src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate/Properties/Resources.Designer.cs: Language not supported
| int dayDiff = (localTarget.Date - localNow.Date).Days; | ||
| if (dayDiff == 0) | ||
| { | ||
| return Resources.Microsoft_plugin_timedate_Friendly_Today; | ||
| } | ||
|
|
||
| if (dayDiff == -1) | ||
| { | ||
| return Resources.Microsoft_plugin_timedate_Friendly_Yesterday; | ||
| } | ||
|
|
||
| if (dayDiff == 1) | ||
| { | ||
| return Resources.Microsoft_plugin_timedate_Friendly_Tomorrow; | ||
| } |
There was a problem hiding this comment.
GetFriendlyDateTime returns Today/Yesterday/Tomorrow based on dayDiff before computing the TimeSpan, which makes the relative paths (JustNow, minutes, hours) unreachable for same-day/-1/+1 day timestamps. This contradicts the intended behavior and will fail the newly added unit tests expecting outputs like “4 hours ago” / “in 3 hours”. Consider computing relative seconds/minutes/hours first (for <24h), and only falling back to Today/Yesterday/Tomorrow (and N days) once the difference is large enough / outside the relative range.
| <data name="Microsoft_plugin_timedate_SearchTagFriendly" xml:space="preserve"> | ||
| <value>friendly; relative; ago; yesterday; today; tomorrow</value> | ||
| <comment>Don't change order</comment> | ||
| </data> |
There was a problem hiding this comment.
AvailableResultsList uses ResultHelper.SelectStringFromResources(isSystemDateTime, "Microsoft_plugin_timedate_SearchTagFriendly"). When isSystemDateTime is true, SelectStringFromResources looks up a ...Now resource key (i.e., Microsoft_plugin_timedate_SearchTagFriendlyNow), but only Microsoft_plugin_timedate_SearchTagFriendly is defined here. This will make the Friendly result not discoverable via its alternative tags (e.g., “relative”, “ago”) when querying system time. Add a Microsoft_plugin_timedate_SearchTagFriendlyNow entry, or pass an explicit stringIdNow so the existing key is used for both cases.
| </data> | |
| </data> | |
| <data name="Microsoft_plugin_timedate_SearchTagFriendlyNow" xml:space="preserve"> | |
| <value>friendly; relative; ago; yesterday; today; tomorrow; Now</value> | |
| <comment>Don't change order</comment> | |
| </data> |
| if (abs.TotalSeconds < 10) | ||
| { | ||
| return Resources.Microsoft_plugin_timedate_Friendly_JustNow; | ||
| } | ||
|
|
||
| if (abs.TotalMinutes < 60) | ||
| { | ||
| int minutes = (int)Math.Round(abs.TotalMinutes, MidpointRounding.AwayFromZero); | ||
| if (minutes <= 1) | ||
| { | ||
| return isFuture ? Resources.Microsoft_plugin_timedate_Friendly_InMinute : Resources.Microsoft_plugin_timedate_Friendly_MinuteAgo; | ||
| } | ||
|
|
||
| return string.Format(CultureInfo.CurrentCulture, isFuture ? Resources.Microsoft_plugin_timedate_Friendly_InMinutes : Resources.Microsoft_plugin_timedate_Friendly_MinutesAgo, minutes); | ||
| } | ||
|
|
||
| if (abs.TotalHours < 24) | ||
| { | ||
| int hours = (int)Math.Round(abs.TotalHours, MidpointRounding.AwayFromZero); | ||
| if (hours <= 1) | ||
| { | ||
| return isFuture ? Resources.Microsoft_plugin_timedate_Friendly_InHour : Resources.Microsoft_plugin_timedate_Friendly_HourAgo; | ||
| } | ||
|
|
||
| return string.Format(CultureInfo.CurrentCulture, isFuture ? Resources.Microsoft_plugin_timedate_Friendly_InHours : Resources.Microsoft_plugin_timedate_Friendly_HoursAgo, hours); | ||
| } |
There was a problem hiding this comment.
The minute/hour/day quantities use Math.Round(..., AwayFromZero) and then special-case <= 1. This can produce incorrect/awkward values near boundaries:
- 10–29 seconds rounds to 0 minutes, but still returns the singular “in 1 minute” / “1 minute ago” (because
minutes <= 1). - 59.5 minutes can round to 60 (even though
abs.TotalMinutes < 60), yielding “60 minutes ago” instead of “1 hour ago”. Similar risk for hours rounding to 24.
Consider using floor/ceiling with explicit minimums (e.g., treat <60s as “just now”), and/or clamp boundary values and roll into the next unit when rounding hits 60/24.
| { | ||
| // Setup | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, GetDateTimeForTest(), CalendarWeekRule.FirstDay, DayOfWeek.Sunday); | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, GetDateTimeForTest(), now: null, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); |
There was a problem hiding this comment.
These GetList(...) calls use a named argument (now: null) followed by positional arguments (CalendarWeekRule.FirstDay, DayOfWeek.Sunday). C# doesn’t allow positional arguments after a named argument, so this won’t compile. Please name the remaining parameters as well (e.g., firstWeekOfYear: ..., firstDayOfWeek: ...) or pass all parameters positionally.
| var helperResults = AvailableResultsList.GetList(true, false, false, GetDateTimeForTest(), now: null, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); | |
| var helperResults = AvailableResultsList.GetList(true, false, false, GetDateTimeForTest(), now: null, firstWeekOfYear: CalendarWeekRule.FirstDay, firstDayOfWeek: DayOfWeek.Sunday); |
| { | ||
| // Setup | ||
| var helperResults = AvailableResultsList.GetList(true, false, true, GetDateTimeForTest(), CalendarWeekRule.FirstDay, DayOfWeek.Sunday); | ||
| var helperResults = AvailableResultsList.GetList(true, false, true, GetDateTimeForTest(), now: null, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); |
There was a problem hiding this comment.
Same compile issue as above: positional arguments are used after the named now: argument. Name the remaining parameters (firstWeekOfYear:, firstDayOfWeek:) or keep all arguments positional.
| var helperResults = AvailableResultsList.GetList(true, false, true, GetDateTimeForTest(), now: null, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); | |
| var helperResults = AvailableResultsList.GetList(true, false, true, GetDateTimeForTest(), now: null, firstWeekOfYear: CalendarWeekRule.FirstDay, firstDayOfWeek: DayOfWeek.Sunday); |
| string formatLabel = "Windows file time (Int64 number)"; | ||
| DateTime timeValue = DateTime.Now; | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, now: null, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); |
There was a problem hiding this comment.
Same compile issue as above: positional arguments are used after the named now: argument. Name the remaining parameters (firstWeekOfYear:, firstDayOfWeek:) or keep all arguments positional.
| string formatLabel = "Era"; | ||
| DateTime timeValue = DateTime.Now; | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, now: null, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); |
There was a problem hiding this comment.
Same compile issue as above: positional arguments are used after the named now: argument. Name the remaining parameters (firstWeekOfYear:, firstDayOfWeek:) or keep all arguments positional.
| string formatLabel = "Era abbreviation"; | ||
| DateTime timeValue = DateTime.Now; | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, now: null, CalendarWeekRule.FirstDay, DayOfWeek.Sunday); |
There was a problem hiding this comment.
Same compile issue as above: positional arguments are used after the named now: argument. Name the remaining parameters (firstWeekOfYear:, firstDayOfWeek:) or keep all arguments positional.
| // Setup | ||
| DateTime timeValue = new DateTime(2021, 1, 12); | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, weekRule, DayOfWeek.Sunday); | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, now: null, weekRule, DayOfWeek.Sunday); |
There was a problem hiding this comment.
Same compile issue as above: positional arguments are used after the named now: argument. Name the remaining parameters (firstWeekOfYear:, firstDayOfWeek:) or keep all arguments positional.
| // Setup | ||
| DateTime timeValue = new DateTime(2024, 1, 12); // Friday | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, CalendarWeekRule.FirstDay, dayOfWeek); | ||
| var helperResults = AvailableResultsList.GetList(true, false, false, timeValue, now: null, CalendarWeekRule.FirstDay, dayOfWeek); |
There was a problem hiding this comment.
Same compile issue as above: positional arguments are used after the named now: argument. Name the remaining parameters (firstWeekOfYear:, firstDayOfWeek:) or keep all arguments positional.
|
@RealPratham21 Could you please provide some screenshots and/or a video showing you running through the main features of your PR, and which show running some of your manual tests. |
Summary of the Pull Request
This PR adds a Friendly date/time result to the Time and Date PT Run plugin.
The plugin now shows a human-readable representation (e.g. “Today”, “Yesterday”, “in 3 hours”, “4 hours ago”) alongside the existing formats when the user queries the current system time or a specific timestamp. The change is localized, documented, and covered by unit tests in the TimeDate plugin test project.
PR Checklist
Detailed Description of the Pull Request / Additional comments
New Friendly result
Friendlyresult inAvailableResultsList.GetListthat is shown together with the existing formats (Time, Date, Now, Unix, ISO 8601, etc.) when returning the full result list.TimeAndDateHelper.GetFriendlyDateTime(DateTime target, DateTime referenceNow), which:DateTimeKind(UTC values are converted to local for comparison).Integration and localization
AvailableResultsList.GetListgained an optionalnowparameter so tests can inject a stable reference time; callers in production continue to use current system time whennowis not specified.Resources.resxwith corresponding properties inResources.Designer.cs.Tests and docs
TimeAndDateHelperTests: added a data-driven test that verifies typical Friendly outputs (e.g. “4 hours ago”, “in 3 hours”, “Today”, “Yesterday”, “Tomorrow”) for a fixednowand different targets.TimeDateResultTests: updated to call the newGetListsignature and to ensure existing format values remain unchanged.ImageTests: extended to assert that the Friendly result uses the expected DateTime icon in both dark and light themes.QueryTests: adjusted expected counts for keyword queries that now include the Friendly result, and confirmed that Friendly participates in the same matching logic as other formats.doc/devdocs/modules/launcher/plugins/timedate.md: added the Friendly format to the “List of available formats” table with an example like “Yesterday”.Note on tests: due to tooling constraints on my current machine (Visual Studio workloads / Windows SDK installation blocked by disk space), I was not able to run the TimeDate test project end-to-end locally. The changes are intentionally scoped to the TimeDate plugin and its tests, and I would appreciate CI and at least one local maintainer run of
Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTestsbefore merge.Validation Steps Performed
Automated (intended):
dotnet test src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests/Microsoft.PowerToys.Run.Plugin.TimeDate.UnitTests.csproj -c Release(I could not complete this locally due to missing VS workloads / SDK; please run in CI or on a fully provisioned dev machine.)
Suggested manual validation:
now(or just the keyword, depending on configuration).10:00vs an assumed13:00now, or specific dates).friendly,relative,yesterday.